/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#ifndef XERN_MESSAGE_HPP
#define XERN_MESSAGE_HPP

#include <boost/shared_ptr.hpp>
#include <boost/uuid/uuid.hpp>
#include "Xern/Vector.hpp"
#include "Xern/Quaternion.hpp"
#include "Xern/DataStream.hpp"
#include "MessageTemplate.hpp"

namespace XernMetaverse
{
	using namespace Xern;
    using namespace boost::uuids;

    namespace detail
    {
        // MessageValueSetter helepr metafunction.
        template<typename Class, typename T>
        struct MessageValueSetter {};

        template<typename Class>
        struct MessageValueSetter<Class, unsigned int>
        {
            static void Set(Class &var, const unsigned int &value)
            {
                var.SetUIntValue(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, signed int>
        {
            static void Set(Class &var, const signed int &value)
            {
                var.SetSIntValue(value);
            }
        };


        template<typename Class>
        struct MessageValueSetter<Class, uint64_t>
        {
            static void Set(Class &var, const uint64_t &value)
            {
                var.SetUInt64Value(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, int64_t>
        {
            static void Set(Class &var, const int64_t &value)
            {
                var.SetSInt64Value(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, float>
        {
            static void Set(Class &var, const float &value)
            {
                var.SetFloatValue(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, double>
        {
            static void Set(Class &var, const double &value)
            {
                var.SetDoubleValue(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, boost::uuids::uuid>
        {
            static void Set(Class &var, const boost::uuids::uuid &value)
            {
                var.SetGuidValue(value);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, std::string>
        {
            static void Set(Class &var, const std::string &value)
            {
                size_t len = value.size() + 1; // Add the trailing \0.
                var.SetBufferSize(len);
                memcpy(var.GetBuffer(), value.c_str(), len);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, Xern::Vector3>
        {
            static void Set(Class &var, const Xern::Vector3 &value)
            {
                var.SetVectorValue(value.x, value.y, value.z);
            }
        };

        template<typename Class>
        struct MessageValueSetter<Class, Xern::Quaternion>
        {
            static void Set(Class &var, const Xern::Quaternion &value)
            {
            	float length = value.Length();
            	assert(CompareFloat(length, 0.0) != 0);
            	length = 1.0f/length;
            	float x, y, z;
            	if(value.w >= 0)
            	{
            		x = value.x;
            		y = value.y;
            		z = value.z;
            	}
            	else
            	{
            		x = -value.x;
            		y = -value.y;
            		z = -value.z;
            	}

                var.SetVectorValue(x*length, y*length, z*length);
            }
        };

        // MessageValueGetter helper metafunction.
        template<typename Class, typename T>
        struct MessageValueGetter {};

        template<typename Class>
        struct MessageValueGetter<Class, unsigned int>
        {
            static unsigned int Get(const Class &var)
            {
                return var.GetUIntValue();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, signed int>
        {
            static signed int Get(const Class &var)
            {
                return var.GetSIntValue();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, uint64_t>
        {
            static uint64_t Get(const Class &var)
            {
                return var.GetUInt64Value();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, int64_t>
        {
            static int64_t Get(const Class &var)
            {
                return var.GetSInt64Value();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, float>
        {
            static float Get(const Class &var)
            {
                return var.GetFloatValue();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, double>
        {
            static double Get(const Class &var)
            {
                return var.GetDoubleValue();
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, boost::uuids::uuid>
        {
            static boost::uuids::uuid Get(const Class &var)
            {
                return var.GetGuidValue();
            }
        };


        template<typename Class>
        struct MessageValueGetter<Class, std::string>
        {
            static std::string Get(const Class &var)
            {
                size_t size = var.GetBufferSize();
                if(size == 0)
                    return std::string();
                char *start = (char*)var.GetBuffer();
                return std::string(start, start + size);
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, Xern::Vector3>
        {
            static Xern::Vector3 Get(const Class &var)
            {
                Xern::Vector3 ret;
                var.GetVectorValue(ret.x, ret.y, ret.z);
                return ret;
            }
        };

        template<typename Class>
        struct MessageValueGetter<Class, Xern::Quaternion>
        {
            static Xern::Quaternion Get(const Class &var)
            {
                Xern::Quaternion ret;
                var.GetVectorValue(ret.x, ret.y, ret.z);
                float xyzsum = 1.0 - ret.x*ret.x - ret.y*ret.y - ret.z*ret.z;
                if(CompareFloat(xyzsum, 0.0f))
                	ret.w = squareroot(xyzsum);
                return ret;
            }
        };

    };

    class MessageBlock;
    class MessageVariable
    {
    public:
        MessageVariable(int id, const MessageBlock *p);
        ~MessageVariable();

        const MessageVariableTemplate &GetTemplate() const;

        const MessageBlock &GetParent() const;

        // Don't use overloading because it can be potentially dangerous.
        void SetUIntValue(unsigned int value);
        void SetSIntValue(signed int value);
        void SetUInt64Value(uint64_t value);
        void SetSInt64Value(int64_t value);
        void SetGuidValue(const uuid &value);
        void SetFloatValue(float value);
        void SetDoubleValue(double value);

        void SetVectorValue(float x, float y, float z);
        void SetVectorValue(double x, double y, double z);
        void SetVectorValue(float x, float y, float z, float w);

        // Use template based overloading because it can be more explicit.
        template<typename T>
        void SetValue(const T &value)
        {
            detail::MessageValueSetter<MessageVariable, T>::Set(*this, value);
        }

        unsigned int GetUIntValue() const;
        signed int GetSIntValue() const;
        uint64_t GetUInt64Value() const;
        int64_t GetSInt64Value() const;
        float GetFloatValue() const;
        double GetDoubleValue() const;
        uuid GetGuidValue() const;

        void GetVectorValue(float &x, float &y, float &z) const;
        void GetVectorValue(double &x, double &y, double &z) const;
        void GetVectorValue(float &x, float &y, float &z, float &w) const;

        template<typename T>
        T GetValue() const
        {
            return detail::MessageValueGetter<MessageVariable, T>::Get(*this);
        }

        // Variable-length parameters uses an special way to handle.
        size_t GetBufferSize() const;
        void SetBufferSize(size_t size);

        void *GetBuffer();
        const void *GetBuffer() const;

        size_t GetSize() const;
        void Write(DataStream &out);
        void Read(DataStream &in);

    private:
        MessageVariable(const MessageVariable &c) {}

        int variableId;
        const MessageBlock *parent;

        struct
        {
            unsigned int uvalue;
            signed int svalue;
            int64_t slvalue;
            uint64_t ulvalue;
            float fvalue[4];
            double dvalue[3];
        };

        uuid guidValue;
        unsigned char *buffer;
        size_t bufferSize;
    };

    class Message;
    class MessageBlock
    {
    public:
        MessageBlock(int id, const Message *p);
        ~MessageBlock();

        boost::shared_ptr<const MessageBlockTemplate> GetTemplate() const;

        MessageVariable &GetVariable(int id, int subblock = 0);
        const MessageVariable &GetVariable(int id, int subblock= 0) const;

        MessageVariable &GetVariable(const std::string &name, int subblock = 0);
        const MessageVariable &GetVariable(const std::string &name, int subblock = 0) const;

        void SetNumberOfSubBlocks(int count);
        int GetNumberOfSubBlocks() const;

        size_t GetSize() const;
        void Write(DataStream &out);
        void Read(DataStream &in);

    private:
        MessageBlock(const MessageBlock &o) {}

        int subcount;
        int blockId;
        const Message *parent;
        std::vector<boost::shared_ptr<MessageVariable> > variables;
    };

    class Message
    {
    public:
        Message(boost::shared_ptr<const MessageTemplate> tmpl);
        ~Message();

        boost::shared_ptr<const MessageTemplate> GetTemplate() const;

        size_t GetSize() const;
        void WriteMessage(DataStream &out);
        void ReadMessage(DataStream &in);

        MessageBlock &GetBlock(int id);
        const MessageBlock &GetBlock(int id) const;

        MessageBlock &GetBlock(const std::string &name);
        const MessageBlock &GetBlock(const std::string &name) const;

        bool isReliable;
        unsigned int sequenceNumber;
    private:
        boost::shared_ptr<const MessageTemplate> messageTemplate;
        std::vector<boost::shared_ptr<MessageBlock> > blocks;
    };

    boost::shared_ptr<Message> CreateMessage(const MessageTemplates &templates,
        MessageTemplate::Frequency freq, unsigned int id);
    boost::shared_ptr<Message> CreateMessage(const MessageTemplates &templates, const std::string &name);
};

#endif // XERN_MESSAGE_HPP
